#include "znx_core_conf.h"
#include "comm/znx_yaml_parser.h"
#include "comm/znx_cpuinfo.h"
#include "comm/znx_module.h"

znx_core_conf_t             *g_core_conf = NULL;


static znx_bool_t znx_core_worker_threads_set(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);

static znx_bool_t znx_core_modules_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);


static znx_command_t znx_core_commands[] = {
    {
        ZNX_STRING("worker_threads"),
        znx_core_worker_threads_set,
        0
    },

    {
        ZNX_STRING("tcp_accepter_threads"),
        znx_command_set_int64,
        offsetof(znx_core_conf_t, tcp_accepter_threads)
    },

    {
        ZNX_STRING("multiple_accept_num"),
        znx_command_set_int64,
        offsetof(znx_core_conf_t, multiple_accept_num)
    },

    {
        ZNX_STRING("thread_session_pool_size"),
        znx_command_set_int64,
        offsetof(znx_core_conf_t, thread_session_pool_size)
    },

    {
        ZNX_STRING("g_mem_freelists_level"),
        znx_command_set_int64,
        offsetof(znx_core_conf_t, g_mem_freelists_level)
    },

    {
        ZNX_STRING("g_aligned_mem_freelists_level"),
        znx_command_set_int64,
        offsetof(znx_core_conf_t, g_aligned_mem_freelists_level)
    },

    {
        ZNX_STRING("worker_mem_freelist_level"),
        znx_command_set_int64,
        offsetof(znx_core_conf_t, worker_mem_freelist_level)
    },

    {
        ZNX_STRING("worker_aligned_mem_freelists_level"),
        znx_command_set_int64,
        offsetof(znx_core_conf_t, worker_aligned_mem_freelists_level)
    },

    {
        ZNX_STRING("ssl_session_cache_size"),
        znx_command_set_int64,
        offsetof(znx_core_conf_t, ssl_session_cache_size)
    },

    {
        ZNX_STRING("listener_conf_path"),
        znx_command_set_string,
        offsetof(znx_core_conf_t, listener_conf_path)
    },

    {
        ZNX_STRING("http_conf_dir"),
        znx_command_set_string,
        offsetof(znx_core_conf_t, http_conf_dir)
    },

    {
        ZNX_STRING("modules"),
        znx_core_modules_parse,
        0
    },

    znx_null_command
};


static znx_bool_t
znx_core_worker_threads_set(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_SCALAR_NODE) {
        ZNX_STR_SET(err, "worker_threads value node type is not SCALAR_NODE");
        return ZNX_FALSE;
    }

    znx_str_t           value;
    int64_t             value_num = 0;
    value.data = (u_char *)node->data.scalar.value;
    value.len = node->data.scalar.length;

    if (value.len == 4 &&
        znx_strncmp(value.data, "auto", 4) == 0)
    {
        g_core_conf->worker_threads = znx_cpu_num();
        return ZNX_TRUE;
    }

    if (!znx_str_to_int64(&value, &value_num)) {
        size_t err_buf_len = value.len + 128;
        err->data = znx_pool_malloc(pool, err_buf_len);
        err->len = (size_t)(znx_snprintf(err->data, err_buf_len,
            "worker_threads value %v is not a number value", &value) - err->data);
        return ZNX_FALSE;
    }

    if (value_num <= 0) {
        ZNX_STR_SET(err, "worker_threads value must be greater than 0 ");
        return ZNX_FALSE;
    }

    g_core_conf->worker_threads = value_num;
    return ZNX_TRUE;
}


static znx_bool_t
znx_core_modules_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_MAPPING_NODE) {
        ZNX_STR_SET(err, "core modules type is not mapping node");
        return ZNX_FALSE;
    }

    yaml_node_pair_t            *pair;
    yaml_node_t                 *key;
    yaml_node_t                 *value;
    size_t                      err_buf_len = command->name.len + 128;

    for (pair = node->data.mapping.pairs.start;
        pair < node->data.mapping.pairs.top;
        pair++)
    {
        key = yaml_document_get_node(doc, pair->key);
        value = yaml_document_get_node(doc, pair->value);

        if (key->type != YAML_SCALAR_NODE) {
            err->data = znx_pool_malloc(pool, err_buf_len);
            err->len = (size_t)(znx_snprintf(err->data, err_buf_len,
                "command %v element key node type must is YAML_SCALAR_NODE",
                &command->name) - err->data);
            return ZNX_FALSE;
        }

        for (int i = 0; i < core_modules_num; i++) {
            if (key->data.scalar.length == core_modules[i]->name.len &&
                znx_strncmp(key->data.scalar.value,
                    core_modules[i]->name.data,
                    core_modules[i]->name.len) == 0)
            {
                if (core_modules[i]->conf_parse) {
                    if (!core_modules[i]->conf_parse(g_core_conf,
                        pool, doc, value, err))
                    {
                        return ZNX_FALSE;    
                    }
                }

                break;
            }
        }
    }

    return ZNX_TRUE;
}


void
znx_core_conf_create()
{
    znx_pool_t      *pool;

    pool = znx_pool_create_ext(ZNX_POOL_MIN_PAGE_SIZE);
    g_core_conf = znx_pool_calloc(pool, sizeof(znx_core_conf_t));
    g_core_conf->pool = pool;

    g_core_conf->worker_threads = ZNX_INT64_UNSET;
    g_core_conf->tcp_accepter_threads = ZNX_INT64_UNSET;
    g_core_conf->multiple_accept_num = ZNX_INT64_UNSET;
    g_core_conf->thread_session_pool_size = ZNX_INT64_UNSET;
    g_core_conf->g_mem_freelists_level = ZNX_INT64_UNSET;
    g_core_conf->g_aligned_mem_freelists_level = ZNX_INT64_UNSET;
    g_core_conf->worker_mem_freelist_level = ZNX_INT64_UNSET;
    g_core_conf->worker_aligned_mem_freelists_level = ZNX_INT64_UNSET;
    g_core_conf->ssl_session_cache_size = ZNX_INT64_UNSET;
    g_core_conf->modules = znx_pool_calloc(pool,
        sizeof(void *) * (size_t)core_modules_num);
}


void
znx_core_conf_destroy()
{
    if (g_core_conf) {
        znx_pool_destroy(g_core_conf->pool);
    }
}


static znx_bool_t
znx_core_conf_check_and_fix()
{
    znx_str_t   *http_conf_dir;
    znx_str_t   *http_main_conf_path;
    znx_str_t   *http_sites_enabled_dir;
    u_char      *ptr;
    size_t      len;

    if (g_core_conf->worker_threads == ZNX_INT64_UNSET) {
        g_core_conf->worker_threads = 1;
    }

    if (g_core_conf->tcp_accepter_threads == ZNX_INT64_UNSET) {
        g_core_conf->tcp_accepter_threads = 0;
    }

    if (g_core_conf->multiple_accept_num == ZNX_INT64_UNSET) {
        g_core_conf->multiple_accept_num = 0;
    }

    if (g_core_conf->thread_session_pool_size == ZNX_INT64_UNSET) {
        g_core_conf->thread_session_pool_size = 0;
    }

    if (g_core_conf->g_mem_freelists_level == ZNX_INT64_UNSET) {
        g_core_conf->g_mem_freelists_level = 0;
    }

    if (g_core_conf->g_aligned_mem_freelists_level == ZNX_INT64_UNSET) {
        g_core_conf->g_aligned_mem_freelists_level = 0;
    }

    if (g_core_conf->worker_mem_freelist_level == ZNX_INT64_UNSET) {
        g_core_conf->worker_mem_freelist_level = 0;
    }

    if (g_core_conf->worker_aligned_mem_freelists_level == ZNX_INT64_UNSET) {
        g_core_conf->worker_aligned_mem_freelists_level = 0;
    }

    if (g_core_conf->ssl_session_cache_size == ZNX_INT64_UNSET) {
        g_core_conf->ssl_session_cache_size = 0;
    }

    if (g_core_conf->listener_conf_path.len == 0) {
        ZNX_STR_SET(&g_core_conf->err, "invalid listener_conf_path");
        return ZNX_FALSE;
    }

    http_conf_dir = &g_core_conf->http_conf_dir;

    if (http_conf_dir->len == 0) {
        ZNX_STR_SET(&g_core_conf->err, "invalid http_conf_dir");
        return ZNX_FALSE;
    }

    if (http_conf_dir->data[http_conf_dir->len - 1] != '/') {
        ptr = znx_pool_calloc(g_core_conf->pool, http_conf_dir->len + 2);
        znx_memcpy(ptr, http_conf_dir->data, http_conf_dir->len);
        ptr[http_conf_dir->len] = '/';
        ptr[http_conf_dir->len + 1] = '\0';
        http_conf_dir->data = ptr;
        http_conf_dir->len += 1;
    }

    http_main_conf_path = &g_core_conf->http_main_conf_path;
    len = http_conf_dir->len + strlen("main.yaml");
    ptr = znx_pool_calloc(g_core_conf->pool, len + 2);
    znx_snprintf(ptr, len + 2, "%vmain.yaml", http_conf_dir);
    http_main_conf_path->data = ptr;
    http_main_conf_path->len = len;

    http_sites_enabled_dir = &g_core_conf->http_sites_enabled_dir;
    len = http_conf_dir->len + strlen("sites_enabled/");
    ptr = znx_pool_calloc(g_core_conf->pool, len + 2);
    znx_snprintf(ptr, len + 2, "%vsites_enabled/", http_conf_dir);
    http_sites_enabled_dir->data = ptr;
    http_sites_enabled_dir->len = len;

    return ZNX_TRUE;
}


znx_bool_t
znx_core_conf_parse(const char *filepath)
{
    znx_core_conf_create();

    if (!znx_yaml_map_conf_parser(g_core_conf,
        g_core_conf->pool, filepath,
        znx_core_commands, &g_core_conf->err))
    {
        return ZNX_FALSE;
    }

    if (!znx_core_conf_check_and_fix()) {
        return ZNX_FALSE;
    }

    return znx_core_modules_conf_post_exec();
}


void
znx_core_conf_create_default()
{
    znx_core_conf_create();

    ZNX_STR_SET(&g_core_conf->listener_conf_path, "/tmp");
    ZNX_STR_SET(&g_core_conf->http_conf_dir, "/tmp");

    if (!znx_core_conf_check_and_fix()) {
        ZNX_DEF_LOG_FATAL(NULL, "create default core conf failed, err: %v", &g_core_conf->err);
    }

    if (!znx_core_modules_conf_post_exec()) {
        ZNX_DEF_LOG_FATAL(NULL, "create default core conf failed, err: %v", &g_core_conf->err);
    }
}